use std::os::raw::{c_char, c_void};
use libloading::Library;
use serde::{Serialize};
use serde_json::Value;
use std::time::{Duration, SystemTime};
use tauri::{AppHandle, Emitter, Manager};
use std::sync::{Arc, Mutex, Once};
use std::thread;

use std::ffi::CString;
use std::fs;
use std::path::Path;
use std::io::Cursor;

// 定义C语言中的函数指针类型
type JlLoadUfwData = extern "C" fn(pFirmwareData: *const c_char, uiFirmwareData: u32) -> i32;
type JlGetUfwCrc = extern "C" fn(pu16Crc: *mut u16, pu32Crc: *mut u32) -> i32;
type JlGetUfwPidVid = extern "C" fn(
    pPid: *mut c_char,
    u32PidLength: u32,
    pVid: *mut c_char,
    u32VidLength: u32,
) -> i32;

type JlUpgradeProgressCallback = extern "stdcall" fn(u32Progress: u32);

type JlUpgradeDevice = extern "C" fn(
    chDrv: c_char,
    bEraseAllFlash: i32,
    bReset: i32,
    callback: JlUpgradeProgressCallback,
) -> i32;

type JlQueryDevicePidVid = extern "C" fn(
    chDrv: c_char,
    pPid: *mut c_char,
    u32PidLength: u32,
    pVid: *mut c_char,
    u32VidLength: u32,
) -> i32;

type JlUpgradeQueryDesc = extern "C" fn(
    chDrv: c_char,
    pBuffer: *mut c_char,
    u32DescBufferLength: u32,
) -> i32;

type JlClearPcModeName = extern "C" fn();
type JlSetPcModeWaitTime = extern "C" fn(u32WaitTime: u32);
type JlAddPcModeName = extern "C" fn(pName: *const c_char);
type JlGetChipDeviceName = extern "C" fn(
    pBuffer: *mut c_char,
    u32BufferLength: u32,
);

static INIT: Once = Once::new();
static APP_HANDLE: Mutex<Option<AppHandle>> = Mutex::new(None);

struct State {
    lib: Option<Library>
}

impl State {
    fn new() -> Self {
        Self {
            lib: None
        }
    }
}

#[derive(Serialize, Clone)]
struct ErrInfo {
    error: String,
    error_code: i32
}


#[tauri::command]
fn ota_init(state: tauri::State<Arc<Mutex<State>>>) -> Result<bool, String> {
    let mut state = state.lock().unwrap();
    let lib = match unsafe { Library::new("jl_firmware_upgrade_x64.dll") } {
        Ok(lib) => lib,
        Err(e) => return Err(format!("Failed to load library: {}", e)),
    };
    state.lib = Some(lib);

    Ok(true)
}

// function to calculate file crc32
#[tauri::command]
fn ota_calculate_crc32(app_handle: AppHandle, filename: String, state: tauri::State<Arc<Mutex<State>>>) -> Result<u32, String> {
    // get path for local data
    let local_state = state.lock().unwrap();
    let lib = local_state.lib.as_ref().unwrap();

    let app_dir = match app_handle.path().resolve(&filename, tauri::path::BaseDirectory::LocalData) {
        Ok(path) => path,
        Err(e) => return Err(format!("Failed to get app directory: {}", e)),
    };

    // check if there is file call update.ufw, read all into data
    if !Path::new(app_dir.as_path()).exists() {
        return Err("No firmware file".to_string());
    }

    let data = match fs::read(app_dir) {
        Ok(data) => data,
        Err(e) => return Err(format!("Failed to read firmware file: {}", e)),
    };

    let mut crc32: u32 = 0;
    let mut crc16: u16 = 0;
    unsafe {
        match lib.get::<JlLoadUfwData>(b"JL_loadUfwData") {
            Ok(symbol) => {
                let result = symbol(data.as_ptr() as *const i8, data.len() as u32);
                if result != 0 {
                    return Err("Failed to load UFW data".to_string());
                }
            }

            Err(e) => return Err(e.to_string()),
        };

        match lib.get::<JlGetUfwCrc>(b"JL_getUfwCrc") {
            Ok(symbol) => {
                let result = symbol(&mut crc16, &mut crc32);
                if result != 0 {
                    return Err("Failed to get UFW CRC".to_string());
                }
            }

            Err(e) => return Err(e.to_string()),
        };
    }

    Ok(crc32)
}


#[derive(Serialize, Clone)]
struct OtaFileInfo {
    path: String,
    crc32: u32
}


#[tauri::command]
fn ota_get_file(app_handle: tauri::AppHandle, url: String, state: tauri::State<Arc<Mutex<State>>>) -> Result<String, String> {
    // step 1: get path of update.ufw from local data
    let app_handle_local = &app_handle;
    let app_dir = match app_handle_local.path().resolve("update.ufw", tauri::path::BaseDirectory::LocalData) {
    Ok(path) => path,
    Err(e) => return Err(format!("Failed to get app directory: {}", e)),
    };

    // remove file
    if Path::new(app_dir.as_path()).exists() {
        fs::remove_file(&app_dir).unwrap();
    }

    // step 4: download firmware file
    let app_handle_clone = app_handle.clone();
    tauri::async_runtime::spawn(async move {
        // download the firmware file
        let client = reqwest::Client::new();
        let firmware_response = match client.get(&url).timeout(Duration::new(10, 0)).send().await {
            Ok(response) => response,
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to get firmware data: {}", e)).unwrap();
                return;
            },
        };

        let firmware_info = match firmware_response.json::<Value>().await {
            Ok(ota_info) => ota_info,
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to parse OTA info: {}", e)).unwrap();
                return;
            },
        };

        // print firmware info
        println!("Firmware info: {}", &firmware_info);

        // checking code in response
        if firmware_info["code"].as_u64().unwrap() != 0 {
            app_handle_clone.emit("ota_download_error", "Failed to parse firmware data".to_string()).unwrap();
            return;
        }

        // get crc32 and download url
        let crc32 = match firmware_info["data"]["crc32"].as_str() {
            Some(crc32) => // convert crc32 to u32
                u32::from_str_radix(crc32, 10).unwrap(),
            None => {
                app_handle_clone.emit("ota_download_error", "Failed to get firmare crc32 of data".to_string()).unwrap();
                return;
            },
        };

        let download_url = match firmware_info["data"]["url"].as_str() {
            Some(url) => url.to_string(),
            None => {
                app_handle_clone.emit("ota_download_error", "Failed to get download url".to_string()).unwrap();
                return;
            },
        };

        // if download url is empty, return error
        if download_url.is_empty() {
            app_handle_clone.emit("ota_download_success", 0).unwrap();
            return;
        }

        // download the firmware file
        let firmware_response = match client.get(&download_url).timeout(Duration::new(20, 0)).send().await {
            Ok(response) => response,
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to download firmware data: {}", e)).unwrap();
                return;
            },
        };

        let mut file = match std::fs::File::create(&app_dir) {
            Ok(file) => file,
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to create firmware file: {}", e)).unwrap();
                return;
            },
        };

        let mut content =  Cursor::new(match firmware_response.bytes().await {
            Ok(data) => data,
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to get bytes from firmware data: {}", e)).unwrap();
                return;
            },
        });


        match std::io::copy(&mut content, &mut file) {
            Ok(_) => (),
            Err(e) => {
                app_handle_clone.emit("ota_download_error", format!("Failed to save firmware file: {}", e)).unwrap();
                return;
            }
        }

        // emit the crc32 value and download success
        app_handle_clone.emit("ota_download_success", OtaFileInfo { path: app_dir.to_string_lossy().to_string(), crc32 }).unwrap();
    });

    Ok("pending".to_string())
}


#[tauri::command]
fn ota_verify_file(crc32: u32, file_path: String, state: tauri::State<Arc<Mutex<State>>>) -> Result<bool, String> {
    // get path from filePath
    let app_dir = Path::new(&file_path);
    let local_state = state.lock().unwrap();
    let lib = local_state.lib.as_ref().unwrap();

    // check if there is file call update.ufw, read all into data
    if !Path::new(&app_dir).exists() {
        return Err("No firmware file".to_string());
    }

    let data = match fs::read(&app_dir) {
        Ok(data) => data,
        Err(e) => return Err(format!("Failed to read firmware file: {}", e)),
    };

    let mut l_crc32: u32 = 0;
    let mut l_crc16: u16 = 0;
    unsafe {
        match lib.get::<JlLoadUfwData>(b"JL_loadUfwData") {
            Ok(symbol) => {
                let result = symbol(data.as_ptr() as *const c_char, data.len() as u32);
                if result != 0 {
                    return Err(format!("Failed to load UFW data: {}", result));
                }
            }

            Err(e) => return Err(e.to_string()),
        };

        match lib.get::<JlGetUfwCrc>(b"JL_getUfwCrc") {
            Ok(symbol) => {
                let result = symbol(&mut l_crc16, &mut l_crc32);
                if result != 0 {
                    return Err(format!("Failed to get UFW CRC: {}", result));
                }
            }

            Err(e) => return Err(e.to_string()),
        };

        if l_crc32 != crc32 {
            return Err("CRC32 mismatch".to_string());
        }
    }

    Ok(true)
}

#[derive(Serialize, Clone)]
struct UsbInfo {
    drv: char,
    name: String,
}


#[tauri::command]
fn ota_get_usb_info(app_handle: AppHandle, state: tauri::State<Arc<Mutex<State>>>) -> Result<(), String> {
    let state = state.inner().clone();

    let app_handle = app_handle.clone();
    thread::spawn(move || {
        let mut buffer = vec![0i8; 256];
        let mut local_state = state.lock().unwrap();
        let lib = local_state.lib.as_ref().unwrap();
        unsafe {
            match lib.get::<JlUpgradeQueryDesc>(b"JL_queryDeviceDesc") {
                Ok(symbol) => {
                    for ch in 'c'..='z' {
                        let result = symbol(ch as u8 as c_char, buffer.as_mut_ptr(), buffer.len() as u32);
                        let pbuffer = &buffer;
                        // turn pbuffer to string
                        let pbuffer_u8: Vec<u8> = pbuffer.iter().map(|&c| c as u8).collect();
                        let pbuffer_str = String::from_utf8_lossy(&pbuffer_u8).to_string();
                        println!("result = {} Device found: {} {}", result, &pbuffer_str, pbuffer_str.contains("br28"));

                        if result == 0
                            && pbuffer_str.contains("br28") {
                                // send the pbuffer_str after delete all zero or space
                                let pbuffer_str = pbuffer_str.replace(" ", "");
                                let pbuffer_str = pbuffer_str.replace("\0", "");
                                app_handle.emit("usb_connect_status", UsbInfo { drv: ch, name: pbuffer_str}).unwrap();
                                return;
                        }
                    }
                }

                Err(e) => {
                    app_handle.emit("usb_connect_status_error", ErrInfo { error: e.to_string(), error_code: -100 }).unwrap();
                    return;
                },
            }

            app_handle.emit("usb_connect_status_error", ErrInfo { error: "No device found".to_string(), error_code: -100 }).unwrap();
        }
    });

    Ok(())
}


#[tauri::command]
fn ota_verify_device_and_ufw(app_handle: AppHandle, dev_name: String, ch: char, state: tauri::State<Arc<Mutex<State>>>) -> Result<bool, String> {


    let state_clone = state.inner().clone();
    let dev_name_clone = dev_name.clone();
    let app_handle = app_handle.clone();
    thread::spawn(move || {
        let local_state = state_clone.lock().unwrap();
        let lib = local_state.lib.as_ref().unwrap();
        unsafe {
            let mut buffer = vec![0i8; 256];
            let name = match lib.get::<JlGetChipDeviceName>(b"JL_getChipDeviceName") {
                Ok(symbol) => {
                    symbol(buffer.as_mut_ptr(), buffer.len() as u32);
                    
                    // return string of name
                    let pbuffer = &buffer;
                    let pbuffer_u8: Vec<u8> = pbuffer.iter().map(|&c| c as u8).collect();
                    let pbuffer_str = String::from_utf8_lossy(&pbuffer_u8).to_string();
                    pbuffer_str.trim_end_matches('\0').to_string() // Trim null characters
                }

                Err(e) => {
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to get chip device name: {}", e), error_code: -100 }).unwrap();
                    return;
                },
            };

            // print dev_name and name
            println!("dev_name = {}, name = {} dev_name contains name ? {}", &dev_name_clone, &name, dev_name_clone.contains(&name));

            // compare dev_name and name to see whether they are the same
            if !dev_name_clone.contains(&name) {
                app_handle.emit("ota_verify_device_error", ErrInfo { error: "Device name mismatch".to_string(), error_code: -100 }).unwrap();
                return;
            }

            println!("ch = {}", ch);

            println!("set pc mode wait time");
            match lib.get::<JlSetPcModeWaitTime>(b"JL_setPcModeWaitTime") {
                Ok(symbol) => {
                    symbol(5000);
                }

                Err(e) => {
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to set PC mode wait time: {}", e), error_code: -100 }).unwrap();
                    return;
                }
            }

            println!("clear pc mode name");
            match lib.get::<JlClearPcModeName>(b"JL_clearPcModeName") {
                Ok(symbol) => {
                    symbol();
                }

                Err(e) => {
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to clear PC mode name: {}", e), error_code: -100 }).unwrap();
                    return;
                }
            }

            println!("add pc mode name");
            match lib.get::<JlAddPcModeName>(b"JL_addPcModeName") {
                Ok(symbol) => {
                    let name = CString::new(dev_name_clone).unwrap();
                    symbol(name.into_raw() as *const c_char);
                }

                Err(e) => {
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to add PC mode name: {}", e), error_code: -100 }).unwrap();
                    return;
                }
            }

            // get pid and vid by JlQueryDevicePidVid
            let mut pid = vec![0i8; 28];
            let mut vid = vec![0i8; 28];

            println!("query device pid vid");
            match lib.get::<JlQueryDevicePidVid>(b"JL_queryDevicePidVid") {
                Ok(symbol) => {
                    let result = symbol(ch as u8 as c_char, pid.as_mut_ptr(), pid.len() as u32, vid.as_mut_ptr(), vid.len() as u32);
                    println!("query pid vid result = {}", result);
                    if result != 0 {
                        app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to get PID/VID: {}", result), error_code: result}).unwrap();
                        return;
                    }
                }

                Err(e) => {
                    println!("query pid vid error = {}", e.to_string());
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to get PID/VID: {}", e), error_code: -100 }).unwrap();
                    return;
                }
            };

            pid = pid.iter().map(|&c| {if c as u8 == 255 { 0 } else { c }}).collect();
            vid = vid.iter().map(|&c| {if c as u8 == 255 { 0 } else { c }}).collect();

            let pid_vec: Vec<u8> = pid.iter().map(|&c| c as u8).collect();
            let pid_str = std::str::from_utf8(&pid_vec).unwrap().trim_end_matches('\0');
            let vid_vec: Vec<u8> = vid.iter().map(|&c| c as u8).collect();
            let vid_str = std::str::from_utf8(&vid_vec).unwrap().trim_end_matches('\0');
            println!("pid = {}, vid = {}", pid_str, vid_str);

            // get pid and vid by JlGetUfwPidVid
            let mut pid2 = vec![0i8; 28];
            let mut vid2 = vec![0i8; 28];

            match lib.get::<JlGetUfwPidVid>(b"JL_getUfwPidVid") {
                Ok(symbol) => {
                    let result = symbol(pid2.as_mut_ptr(), pid2.len() as u32, vid2.as_mut_ptr(), vid2.len() as u32);
                    if result != 0 {
                        // wrapper result into error with format
                        app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to get UFW PID/VID: {}", result), error_code: result }).unwrap();
                        return;
                    }
                }

                Err(e) => {
                    app_handle.emit("ota_verify_device_error", ErrInfo { error: format!("Failed to get UFW PID/VID: {}", e), error_code: -100 }).unwrap();
                    return;
                }
            };

            pid2 = pid2.iter().map(|&c| {if c as u8 == 255 {0} else {c}}).collect();
            vid2 = vid2.iter().map(|&c| {if c as u8 == 255 {0} else {c}}).collect();

            // print pid2 and vid2
            
            // convert pid and vid to string
            let pid2_vec: Vec<u8> = pid2.iter().map(|&c| c as u8).collect();
            let vid2_vec: Vec<u8> = vid2.iter().map(|&c| c as u8).collect();
            // print pid2_vec and vid2_vec
            let pid2_str = std::str::from_utf8(&pid2_vec).unwrap().trim_end_matches('\0');
            let vid2_str = std::str::from_utf8(&vid2_vec).unwrap().trim_end_matches('\0');
            
            
            println!("pid2 = {}, vid2 = {}", pid2_str, vid2_str);

            // compare pid and vid to see whether they are the same
            if pid_str != pid2_str || vid_str != vid2_str {
                app_handle.emit("ota_verify_device_error", ErrInfo { error: "PID/VID mismatch".to_string(), error_code: -100 }).unwrap();
                return;
            }

            app_handle.emit("ota_verify_device_success", true).unwrap();
        }
    });

    Ok(true)
}

extern "stdcall" fn progress(progress: u32) {
    let guard = APP_HANDLE.lock().unwrap();
    if let Some(handle) = guard.as_ref() {
        match handle.emit("upgrade_progress", progress) {
            Ok(_) => (),
            Err(e) => eprintln!("Failed to emit upgrade progress: {}", e),
        };
    }
}

#[tauri::command]
fn ota_update_device(app_handle: AppHandle, ch: char, name: String, state: tauri::State<Arc<Mutex<State>>>) -> Result<bool, String> {
    let app_handle = app_handle.clone();
    unsafe {
        let state_clone = state.inner().clone();
        let ch_clone = ch.clone();
        thread::spawn(move || {
            let local_state = state_clone.lock().unwrap();
            let lib_clone = local_state.lib.as_ref().unwrap();

            match lib_clone.get::<JlSetPcModeWaitTime>(b"JL_setPcModeWaitTime") {
                Ok(symbol) => {
                    symbol(5000);
                }
    
                Err(e) => {
                    app_handle.emit("ota_update_error", ErrInfo { error: e.to_string(), error_code: -100 }).unwrap();
                    return;
                }
            }
    
            match lib_clone.get::<JlClearPcModeName>(b"JL_clearPcModeName") {
                Ok(symbol) => {
                    symbol();
                }
    
                Err(e) => {
                    app_handle.emit("ota_update_error", ErrInfo { error: e.to_string(), error_code: -100 }).unwrap();
                    return;
                }
            }
    
            match lib_clone.get::<JlAddPcModeName>(b"JL_addPcModeName") {
                Ok(symbol) => {
                    let name = name.as_ptr() as *mut c_char;
                    symbol(name);
                }
    
                Err(e) => {
                    app_handle.emit("ota_update_error", ErrInfo { error: e.to_string(), error_code: -100 }).unwrap();
                    return;
                }
            }

            match lib_clone.get::<JlUpgradeDevice>(b"JL_upgradeDevice") {
                Ok(symbol) => {
                    let result = symbol(ch_clone as u8 as c_char, 0, 1, progress);
                    if result == 0 {
                        println!("Upgrade successful");
                        app_handle.emit("ota_update_success", ()).unwrap();
                    } else {
                        eprintln!("JL_upgradeDevice failed with result {}", result);
                        app_handle.emit("ota_update_error", ErrInfo { error: "Failed to upgrade device".to_string(), error_code: result }).unwrap();
                    }
                }

                Err(e) => eprintln!("Failed to get JL_upgradeDevice: {}", e),
                }
            });

        Ok(true)
    }
}

// add command to exit app
#[tauri::command]
fn exit_app(app_handle: AppHandle) {
    app_handle.exit(0);
}


#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![
            ota_init,
            ota_get_file,
            ota_verify_file,
            ota_get_usb_info,
            ota_verify_device_and_ufw,
            ota_update_device,
            exit_app,
            ota_calculate_crc32
        ])
        .setup(|app| {
            #[cfg(debug_assertions)] // only include this code on debug builds
            {
                let window = app.get_webview_window("main").unwrap();
                window.open_devtools();
                window.close_devtools();
            }
            INIT.call_once(|| {
                let handle = app.handle();
                *APP_HANDLE.lock().unwrap() = Some(handle.clone());
            });
            app.manage(Arc::new(Mutex::new(State::new())));
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}